package org.alex.spring.core.resolver;


import java.time.*;
import java.util.*;
import java.util.function.Function;

/**
 * 配置解析器
 * 支持3种查询：
 * 1、按key查询 getProperty("app.title")
 * 2、${abc.xyz}查询,如 getProperty("${app.title}")，常用于@Value("${app.title}")注入
 * 3、带默认值 ${abc.xyz:defaultValue}查询，getProperty("${app.title:Summer}")，常用于@Value("${app.title:Summer}")注入
 */
public class PropertyResolver {
    private final Map<String, String> properties = new HashMap<>();

    private final Map<Class<?>, Function<String, Object>> converters = new HashMap<>();

    public PropertyResolver(Properties props) {
        // 环境变量导入
        this.properties.putAll(System.getenv());
        // properties 导入
        Set<String> names = props.stringPropertyNames();
        for (String name : names) {
            this.properties.put(name, props.getProperty(name));
        }

        // 类型转换初始化化
        convertersInit();
    }

    /**
     * 类型转换初始化化
     */
    private void convertersInit() {
        //String
        converters.put(String.class, s -> s);
        // boolean
        converters.put(boolean.class, Boolean::parseBoolean);
        converters.put(Boolean.class, Boolean::valueOf);
        // int
        converters.put(int.class, Integer::parseInt);
        converters.put(Integer.class, Integer::valueOf);
        // other
        // Date/Time
        converters.put(LocalDate.class, LocalDate::parse);
        converters.put(LocalTime.class, LocalTime::parse);
        converters.put(LocalDateTime.class, LocalDateTime::parse);
        converters.put(ZonedDateTime.class, ZonedDateTime::parse);
        converters.put(Duration.class, Duration::parse);
        converters.put(ZoneId.class, ZoneId::of);
    }

    /**
     * 通过key获取property
     */
    public String getProperty(String key) {
        PropertyExpr propertyExpr = parsePropertyExpr(key);
        if (propertyExpr != null) {
            if (propertyExpr.defaultValue() != null) {
                // 带默认值查询
                return getProperty(propertyExpr.key(), propertyExpr.defaultValue());
            } else {
                // 不带默认值查询
                return getRequiredProperty(propertyExpr.key());
            }

        }
        // 普通key查询
        String value = this.properties.get(key);
        if (value != null) {
            return parseValue(value);
        }
        return null;
    }

    /**
     * 通过递归实现嵌套查询 ${key1:${key2:value}}
     */
    private String parseValue(String value) {
        PropertyExpr propertyExpr = parsePropertyExpr(value);
        if (propertyExpr == null) {
            return value;
        }

        if (propertyExpr.defaultValue() != null) {
            return getProperty(propertyExpr.key(), propertyExpr.defaultValue());
        } else {
            return getRequiredProperty(propertyExpr.key());
        }
    }

    /**
     * 通过key获取property
     */
    private String getRequiredProperty(String key) {
        String value = getProperty(key);
        return Objects.requireNonNull(value, "Property '" + key + "' not found");
    }

    /**
     * 带默认值查询
     */
    private String getProperty(String key, String defaultValue) {
        String value = getProperty(key);
        return value == null ? parseValue(defaultValue) : value;
    }

    /**
     * 通过${}获取property
     */
    private PropertyExpr parsePropertyExpr(String key) {
        if (key.startsWith("${") && key.endsWith("}")) {
            // 是否存在defaultValue
            int n = key.indexOf(":");

            if (n == -1) {
                // 没有defaultValue
                String k = key.substring(2, key.length() - 1);
                return new PropertyExpr(k, null);
            }
            // 有defaultValue ${key:value}
            String k = key.substring(2, n);
            return new PropertyExpr(k, key.substring(n + 1, key.length() - 1));
        }
        return null;
    }

    /**
     * 类型转换
     */
    @SuppressWarnings("unchecked")
    <T> T convert(String value, Class<?> clazz) {
        Function<String, Object> fn = this.converters.get(clazz);

        if (fn == null) {
            throw new IllegalArgumentException("Unsupported value type:" + clazz.getName());
        }
        return (T) fn.apply(value);
    }

}
